Because of the way yield
methods are rewritten by the compiler (they become lazily evaluated state machines) any exceptions thrown
during the parameters check will happen only when the collection is iterated over. That could happen far away from the source of the buggy code.
Therefore it is recommended to split the method into two: an outer method handling the validation (no longer lazy) and an inner (lazy) method to
handle the iteration.
This rule raises an issue when a method throws any exception derived from ArgumentException
and contains the yield
keyword.
Noncompliant code example
public static IEnumerable<TSource> TakeWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) // Noncompliant
{
if (source == null) { throw new ArgumentNullException(nameof(source)); }
if (predicate == null) { throw new ArgumentNullException(nameof(predicate)); }
foreach (var element in source)
{
if (!predicate(element)) { break; }
yield return element;
}
}
Compliant solution
public static IEnumerable<TSource> TakeWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null) { throw new ArgumentNullException(nameof(source)); }
if (predicate == null) { throw new ArgumentNullException(nameof(predicate)); }
return TakeWhileIterator<TSource>(source, predicate);
}
private static IEnumerable<TSource> TakeWhileIterator<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
foreach (TSource element in source)
{
if (!predicate(element)) break;
yield return element;
}
}